/*
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package main;

import input.IRCCommand;
import input.Parser;

import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;

import main.UserCentral.AuthenticatedUser;
import main.UserCentral.UserPrefixes;

import org.apache.log4j.Logger;
import org.jibble.pircbot.AlreadyConnectedException;
import org.jibble.pircbot.Colors;
import org.jibble.pircbot.IrcException;
import org.jibble.pircbot.NickAlreadyInUseException;
import org.jibble.pircbot.PircBot;
import org.jibble.pircbot.User;

import output.IRCOutput;
import output.OutputQueue;
import output.OutputTypeEnum;
import server.IntelListener;
import server.IntelServerType;
import server.JettyServer;
import timers.TimerHandler;
import tools.UtopiaTime;
import botobjects.Command;
import botobjects.Nap;

import commandhandlers.ArmyHandler;
import commandhandlers.BotHandler;
import commandhandlers.KDHandler;
import commandhandlers.SpellsOpsHandler;
import commandhandlers.UserHandler;

import database.DBConnectionFactory;

/**
 * This is the main bot class that interacts with IRC
 * 
 * 
 */
public class Bot extends PircBot {
   /**
    * The task to do every tick
    * 
    * 
    */
   private class HourlyTask extends TimerTask {
	  private final Timer timer;

	  public HourlyTask(final Timer timer) {
		 super();
		 this.timer = timer;
	  }

	  @Override
	  public void run() {
		 timer.cancel();
		 tickTimer();
		 hourlyMessage();
	  }
   }

   private static final Bot INSTANCE = new Bot();

   public static final Bot getInstance() {
	  return INSTANCE;
   }

   private boolean plannedDisconnect = false;
   private final TimerHandler timerHandler;
   private Timer tickTimer = null;
   private boolean isAuthorizing = false;

   private final List<String> toAuthenticateList;

   private boolean adminlock = false;

   private final List<Worker> workers;

   private Bot() {
	  super();
	  setAutoNickChange(true);
	  timerHandler = new TimerHandler();

	  if (Main.getBotSettings().listenForIntel()) {
		 startIntelListener();
	  }

	  toAuthenticateList = new LinkedList<String>();

	  workers = Collections.emptyList();
   }

   /**
    * Connects the bot to IRC
    */
   public void connectToIRC() {
	  UserCentral.getInstance().getMainchanUsers().clear();
	  UserCentral.getInstance().getMainChanUserPrefixes().clear();
	  UserCentral.getInstance().getAdminchanUsers().clear();
	  UserCentral.getInstance().getAuthenticatedUsers().clear();
	  toAuthenticateList.clear();
	  UserCentral.getInstance().getAutoNickLinkCheck().clear();
	  setVerbose(false);
	  setName(Main.getBotSettings().getBotNick());
	  setAutoNickChange(true);
	  for (int i = 0; i < 2000; ++i) {
		 boolean connected = true;
		 try {
			connect(Main.getBotSettings().getIrcServer());
		 } catch (final IOException e) {
			Logger.getLogger("Error").error("", e);
			connected = false;
		 } catch (NickAlreadyInUseException e) {
			// auto nick change is set
		 } catch (IrcException e) {
			Logger.getLogger("Error").error("", e);
			connected = false;
		 } catch (AlreadyConnectedException e) {
			disconnectFromIRC();
			connectToIRC();
			return;
		 }
		 if (connected) {
			break;
		 } else {
			try {
			   Thread.sleep(30000);
			} catch (final InterruptedException e) {
			}
		 }
	  }

	  if (isConnected()) {
		 identify(Main.getBotSettings().getBotPassword());

		 reloadTimers();
		 SpellsOpsHandler.removeOldSpells();
		 tickTimer();
	  }
   }

   @Override
   public void disconnect() {
	  synchronized (this) {
		 this.quitServer();
		 for (Worker worker : workers) {
			worker.disconnect();
		 }
	  }
   }

   /**
    * Disconnects the bot from the database
    */
   public void disconnectFromDB() {
	  DBConnectionFactory.getConnectionHandler().close();
   }

   /**
    * Disconnects the bot from IRC, and does not make it attempt to reconnect again
    */
   public void disconnectFromIRC() {
	  plannedDisconnect = true;
	  disconnect();
   }

   /**
    * Used to check the status of users (i.e. whether they own the nick they're on)
    * 
    * @param user
    */
   public void doStatusCheck(final String user) {
	  sendRawLine("ns status " + user);
   }

   /**
    * Not used, so just calling the base equals
    */
   @Override
   public boolean equals(final Object o) {
	  return super.equals(o);
   }

   private int getSource(final String sender, final String channel) {
	  int source = 0;
	  if (sender.equals(channel)) {
		 source = Constants.PM_SOURCE;
	  } else if (Main.getBotSettings().getMainChannel().equals(channel)) {
		 source = Constants.MAIN_CHAN_SOURCE;
	  } else if (Main.getBotSettings().getAdminChannel().equals(channel)) {
		 source = Constants.ADMIN_CHAN_SOURCE;
	  } else {
		 source = Constants.OTHER_CHAN_SOURCE;
	  }
	  return source;
   }

   /**
    * Returns the special hourly message
    * 
    * @return
    */
   private String getSpecialHourlyMessage() {
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  String out = "";
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("SELECT * FROM hourlymessage WHERE id = 0");
		 rs = ps.executeQuery();
		 if (rs.next()) {
			out = rs.getString("message");
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Returns the timer handler object
    * 
    * @return
    */
   public TimerHandler getTimerHandler() {
	  return timerHandler;
   }

   @Override
   public int hashCode() {
	  assert false : "hashCode not designed";
	  return 42; // any arbitrary constant will do
   }

   /**
    * Prints out various messages every tick
    */
   private void hourlyMessage() {
	  DBConnectionFactory.getConnectionHandler(true);
	  // resets the db connection to clear out any lingering statements etc.

	  UtopiaTime ut = new UtopiaTime(GregorianCalendar.getInstance().getTimeInMillis());
	  sendMessage(Main.getBotSettings().getMainChannel(), Colors.OLIVE + "Hour change to " + Colors.BLUE + ut.formattedUT());
	  KDHandler.getScheduledEvents(ut.formattedUT());
	  if (!getSpecialHourlyMessage().equals("")) {
		 sendMessage(Main.getBotSettings().getMainChannel(), Colors.RED + getSpecialHourlyMessage());
	  }
	  List<String> armies = ArmyHandler.getArmiesReturningThisTick(ut.formattedUT());
	  for (final String s : armies) {
		 sendMessage(Main.getBotSettings().getMainChannel(), Colors.OLIVE + "Armies returning this tick: " + Colors.BLUE + s);
	  }
	  for (final String out : SpellsOpsHandler.getSpellsEndingThisHour(ut.formattedUT())) {
		 sendMessage(Main.getBotSettings().getMainChannel(), out);
	  }
	  List<Nap> toremovenaps = new LinkedList<Nap>();
	  for (Nap nap : Nap.getNaps()) {
		 if (nap.getEndDate() > 0 && nap.getEndDate() < GregorianCalendar.getInstance().getTimeInMillis()) {
			sendMessage(Main.getBotSettings().getMainChannel(), Colors.RED + "Nap with " + nap.getKd() + " expired");
			if (!"".equals(Main.getBotSettings().getAdminChannel())) {
			   sendMessage(Main.getBotSettings().getAdminChannel(), Colors.RED + "Nap with " + nap.getKd() + " expired");
			}
			toremovenaps.add(nap);
		 }
	  }
	  for (Nap nap : toremovenaps) {
		 Nap.deleteFromDB(nap.getKd());
	  }
   }

   @Override
   protected void onDeHop(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) && !sourceNick.equals(getNick()) && !workersContain(sourceNick)) {
		 if (UserCentral.getInstance().getMainChanUserPrefixes().containsKey(recipient)) {
			UserCentral
				  .getInstance()
				  .getMainChanUserPrefixes()
				  .put(recipient,
						UserCentral.getInstance().setUserPrefix(
							  UserCentral.getInstance().getMainChanUserPrefixes().get(recipient), false, "h"));
		 } else {
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(recipient, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), false, "h"));
		 }
		 BotHandler.updateSilenceListPrefix(recipient);
	  }
   }

   @Override
   protected void onDeop(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) && !sourceNick.equals(getNick()) && !workersContain(sourceNick)) {
		 if (UserCentral.getInstance().getMainChanUserPrefixes().containsKey(recipient)) {
			UserCentral
				  .getInstance()
				  .getMainChanUserPrefixes()
				  .put(recipient,
						UserCentral.getInstance().setUserPrefix(
							  UserCentral.getInstance().getMainChanUserPrefixes().get(recipient), false, "o"));
		 } else {
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(recipient, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), false, "o"));
		 }
		 BotHandler.updateSilenceListPrefix(recipient);
	  }
   }

   @Override
   protected void onDeOwner(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) && !sourceNick.equals(getNick()) && !workersContain(sourceNick)) {
		 if (UserCentral.getInstance().getMainChanUserPrefixes().containsKey(recipient)) {
			UserCentral
				  .getInstance()
				  .getMainChanUserPrefixes()
				  .put(recipient,
						UserCentral.getInstance().setUserPrefix(
							  UserCentral.getInstance().getMainChanUserPrefixes().get(recipient), false, "q"));
		 } else {
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(recipient, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), false, "q"));
		 }
		 BotHandler.updateSilenceListPrefix(recipient);
	  }
   }

   @Override
   protected void onDeSop(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) && !sourceNick.equals(getNick()) && !workersContain(sourceNick)) {
		 if (UserCentral.getInstance().getMainChanUserPrefixes().containsKey(recipient)) {
			UserCentral
				  .getInstance()
				  .getMainChanUserPrefixes()
				  .put(recipient,
						UserCentral.getInstance().setUserPrefix(
							  UserCentral.getInstance().getMainChanUserPrefixes().get(recipient), false, "a"));
		 } else {
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(recipient, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), false, "a"));
		 }
		 BotHandler.updateSilenceListPrefix(recipient);
	  }
   }

   @Override
   protected void onDeVoice(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) && !sourceNick.equals(getNick()) && !workersContain(sourceNick)) {
		 if (UserCentral.getInstance().getMainChanUserPrefixes().containsKey(recipient)) {
			UserCentral
				  .getInstance()
				  .getMainChanUserPrefixes()
				  .put(recipient,
						UserCentral.getInstance().setUserPrefix(
							  UserCentral.getInstance().getMainChanUserPrefixes().get(recipient), false, "v"));
		 } else {
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(recipient, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), false, "v"));
		 }
		 BotHandler.updateSilenceListPrefix(recipient);
	  }
   }

   @Override
   protected void onDisconnect() {
	  if (!plannedDisconnect) {
		 try {
			Thread.sleep(60000);
		 } catch (final InterruptedException e) {
		 }
		 connectToIRC();
	  }
	  plannedDisconnect = false;
   }

   @Override
   protected void onHop(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) && !sourceNick.equals(getNick()) && !workersContain(sourceNick)) {
		 if (UserCentral.getInstance().getMainChanUserPrefixes().containsKey(recipient)) {
			UserCentral
				  .getInstance()
				  .getMainChanUserPrefixes()
				  .put(recipient,
						UserCentral.getInstance().setUserPrefix(
							  UserCentral.getInstance().getMainChanUserPrefixes().get(recipient), true, "h"));
		 } else {
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(recipient, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), true, "h"));
		 }
		 BotHandler.updateSilenceListPrefix(recipient);
	  }
   }

   @Override
   protected void onInvite(final String targetNick, final String sourceNick, final String sourceLogin,
		 final String sourceHostname, final String channel) {
	  if (targetNick.equals(Main.getBotSettings().getBotNick())
			&& (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) || channel.equalsIgnoreCase(Main.getBotSettings().getAdminChannel()))) {
		 this.joinChannel(channel);
	  }
   }

   @Override
   protected void onJoin(final String channel, final String sender, final String login, final String hostname) {
	  if (!sender.equals(getNick()) && !workersContain(sender) && !Main.getBotSettings().requirePasswordAuth()) {
		 doStatusCheck(sender);
	  }

	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel())) {
		 if (!UserCentral.getInstance().getMainchanUsers().contains(sender)) {
			UserCentral.getInstance().getMainchanUsers().add(sender);
		 }
		 if (!UserCentral.getInstance().getMainChanUserPrefixes().containsKey(sender)) {
			UserCentral.getInstance().getMainChanUserPrefixes().put(sender, new UserPrefixes());
		 }
		 List<Nap> toremovenaps = new LinkedList<Nap>();
		 for (Nap nap : Nap.getNaps()) {
			if (nap.getEndDate() > 0 && nap.getEndDate() < GregorianCalendar.getInstance().getTimeInMillis()) {
			   sendMessage(Main.getBotSettings().getMainChannel(), Colors.RED + "Nap with " + nap.getKd() + " expired");
			   if (!"".equals(Main.getBotSettings().getAdminChannel())) {
				  sendMessage(Main.getBotSettings().getAdminChannel(), Colors.RED + "Nap with " + nap.getKd() + " expired");
			   }
			   toremovenaps.add(nap);
			}
		 }
		 for (Nap nap : toremovenaps) {
			Nap.deleteFromDB(nap.getKd());
		 }
		 KDHandler.removeOldEvents();
		 if (sender.equals(getNick()) && !Main.getBotSettings().requirePasswordAuth()) {
			sendMessage(channel, Colors.RED + "Authenticating people. Please wait...");
		 }
	  } else if (channel.equalsIgnoreCase(Main.getBotSettings().getAdminChannel())
			&& !UserCentral.getInstance().getAdminchanUsers().contains(sender)) {
		 UserCentral.getInstance().getAdminchanUsers().add(sender);
	  }

   }

   @Override
   protected void onKick(final String channel, final String kickerNick, final String kickerLogin, final String kickerHostname,
		 final String recipientNick, final String reason) {
	  if (recipientNick.equals(getNick()) && !workersContain(recipientNick)) {
		 // any point trying to do something?
		 // if someone decides to akick, it could cause an endless loop
	  } else {
		 String nick = CommonMethods.getUserFromDB(recipientNick, false);
		 if ("".equals(nick)) {
			UserHandler.addLastSeen(recipientNick, "Saw " + recipientNick + " getting kicked from " + channel);
		 } else {
			UserHandler.addLastSeen(nick, "Saw " + nick + " getting kicked from " + channel);
		 }

		 if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel())) {
			UserCentral.getInstance().getMainchanUsers().remove(recipientNick);
			UserCentral.getInstance().getMainChanUserPrefixes().remove(recipientNick);
		 } else if (channel.equalsIgnoreCase(Main.getBotSettings().getAdminChannel())) {
			UserCentral.getInstance().getAdminchanUsers().remove(recipientNick);
		 }
		 if (!UserCentral.getInstance().getMainchanUsers().contains(recipientNick)
			   && !UserCentral.getInstance().getAdminchanUsers().contains(recipientNick)) {
			UserCentral.getInstance().unauthenticateUser(recipientNick);
		 }
	  }
   }

   @Override
   protected void onMessage(final String channel, final String sender, final String preferredHandler, final String msg) {
	  if (sender.equals(getNick()) || workersContain(sender) || isAuthorizing)
		 return;

	  IRCOutput output = null;
	  int source = getSource(sender, channel);
	  if (source == Constants.MAIN_CHAN_SOURCE) {
		 Logger.getLogger("Chat").info("<" + sender + "> " + msg);
	  }

	  String formatMessage = Parser.isCommand(msg.trim());
	  if ("".equals(formatMessage)) { // empty string means it wasn't a command
		 if ("".equals(UserCentral.getInstance().getOnlineUser(sender)))
			return;
		 else {
			int newOrdersCheck = KDHandler.getNewOrdersAmount(sender);
			if (newOrdersCheck > 0 && !adminlock
				  && !"".equals(CommonMethods.getProv(UserCentral.getInstance().getOnlineUser(sender)))) {
			   sendNotice(sender, Colors.RED + newOrdersCheck
					 + " new orders have been added since you last checked. Please check "
					 + Main.getBotSettings().getCommandPrefix() + "orders");
			}
			UserHandler.addUserActivity(UserCentral.getInstance().getOnlineUser(sender), sender);
		 }
		 TextInputCentral.addTextInput(msg.trim(), sender, channel, preferredHandler);
	  } else { // it was a command, so parse and handle it
		 IRCCommand command = Parser.parseCommand(formatMessage, source, sender, channel);
		 Command cmdObject = Command.getCommand(command.get(0));
		 if ("".equals(UserCentral.getInstance().getOnlineUser(sender))
			   && (cmdObject == null || !Command.getCommand(command.get(0)).isPublicAccess()) || adminlock
			   && !UserCentral.getInstance().onlineUserIsAdmin(sender, channel))
			return;
		 if (!"".equals(UserCentral.getInstance().getOnlineUser(sender))) {
			UserHandler.addUserActivity(UserCentral.getInstance().getOnlineUser(sender), sender);
		 }
		 CommandCentral.addCommand(command);
	  }
	  send(output);
   }

   @Override
   protected void onNickChange(final String oldNick, final String login, final String hostname, final String newNick) {
	  UserCentral.getInstance().nickUpdate(oldNick, newNick);
	  if (toAuthenticateList.contains(oldNick) && !Main.getBotSettings().requirePasswordAuth()) {
		 toAuthenticateList.remove(oldNick);
		 toAuthenticateList.add(newNick);
	  }
   }

   @Override
   protected void onNotice(final String sourceNick, final String sourceLogin, final String sourceHostname, final String target,
		 final String notice) {
	  if (sourceNick.matches("NickServ.*") && target.equals(getNick())) {
		 if ("Your nickname isn't registered.".equals(notice)) { // means we've been disconnected, and switched to a non
			// registered nick
			sendMessage("NickServ", "GHOST " + Main.getBotSettings().getBotNick() + " " + Main.getBotSettings().getBotPassword());
			try {
			   Thread.sleep(3000);
			} catch (final InterruptedException e) {
			   Logger.getLogger("Error").error("", e);
			}
			changeNick(Main.getBotSettings().getBotNick());
			identify(Main.getBotSettings().getBotPassword());
		 } else if (notice.matches("Password accepted.*")) { // identification succeeded, so we can try to join the main chan
			joinChannel(Main.getBotSettings().getMainChannel()
				  + ("".equals(Main.getBotSettings().getMainChannelPw()) ? "" : " " + Main.getBotSettings().getMainChannelPw()));
		 } else if (notice.matches("STATUS .* [0-3]")) { // the return message for status checks. a 3 means the user owns the
			// nick
			String[] msg = notice.split(" ");
			String nick = "";
			if (msg.length == 3) {
			   nick = msg[1];
			}
			if (msg[2].equals("3")
				  && UserCentral.getInstance().getAutoNickLinkCheck().containsKey(nick)
				  && UserCentral.getInstance().getOnlineUser(nick)
						.equals(UserCentral.getInstance().getAutoNickLinkCheck().get(nick))) {
			   for (String out : UserHandler.linkNick(nick, UserCentral.getInstance().getAutoNickLinkCheck().remove(nick))) {
				  sendNotice(nick, out);
			   }
			} else {
			   String mainNick = CommonMethods.getUserFromDB(nick, false);
			   if (!msg[2].equals("3") || "".equals(mainNick)
					 || UserCentral.getInstance().getAuthenticatedUsers().containsKey(nick)) {
				  // do nothing, as the user is already authenticated, doesn't own the nick or isn't registered at all
			   } else { // display the auth/welcome message
				  UserCentral.getInstance().getAuthenticatedUsers()
						.put(nick, new AuthenticatedUser(mainNick, CommonMethods.userIsAdminDB(mainNick)));
				  if (!isAuthorizing) {
					 sendWelcomeMessage(nick);
				  }
			   }
			   toAuthenticateList.remove(nick);
			   if (toAuthenticateList.isEmpty() && isAuthorizing) {
				  // if the auto-auth on join is finished
				  sendMessage(Main.getBotSettings().getMainChannel(), Colors.RED + "Done");
				  isAuthorizing = false;
				  if (!"".equals(Main.getBotSettings().getAdminChannel())) {
					 joinChannel(Main.getBotSettings().getAdminChannel()
						   + ("".equals(Main.getBotSettings().getAdminChannelPw()) ? "" : " "
								 + Main.getBotSettings().getAdminChannelPw()));
				  }
				  new Thread() { // now that the main bot is done, it's time for the workers to join

					 @Override
					 public void run() {
						for (Worker worker : workers) {
						   synchronized (worker) {
							  worker.connectToIRC();
							  while (!worker.finishedConnecting()) {
								 try {
									worker.wait();
								 } catch (InterruptedException e) {
									Logger.getLogger("Error").error("", e);
								 }
							  }
						   }
						}
					 }
				  }.start();
			   }
			}
		 }
	  }
   }

   @Override
   protected void onOp(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) && !sourceNick.equals(getNick()) && !workersContain(sourceNick)) {
		 if (UserCentral.getInstance().getMainChanUserPrefixes().containsKey(recipient)) {
			UserCentral
				  .getInstance()
				  .getMainChanUserPrefixes()
				  .put(recipient,
						UserCentral.getInstance().setUserPrefix(
							  UserCentral.getInstance().getMainChanUserPrefixes().get(recipient), true, "o"));
		 } else {
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(recipient, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), true, "o"));
		 }
		 BotHandler.updateSilenceListPrefix(recipient);
	  }
   }

   @Override
   protected void onOwner(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) && !sourceNick.equals(getNick()) && !workersContain(sourceNick)) {
		 if (UserCentral.getInstance().getMainChanUserPrefixes().containsKey(recipient)) {
			UserCentral
				  .getInstance()
				  .getMainChanUserPrefixes()
				  .put(recipient,
						UserCentral.getInstance().setUserPrefix(
							  UserCentral.getInstance().getMainChanUserPrefixes().get(recipient), true, "q"));
		 } else {
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(recipient, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), true, "q"));
		 }
		 BotHandler.updateSilenceListPrefix(recipient);
	  }
   }

   @Override
   protected void onPart(final String channel, final String sender, final String login, final String hostname) {
	  String nick = CommonMethods.getUserFromDB(sender, false);
	  if ("".equals(nick)) {
		 UserHandler.addLastSeen(sender, "Saw " + sender + " leaving channel " + channel);
	  } else {
		 UserHandler.addLastSeen(nick, "Saw " + nick + " leaving channel " + channel);
	  }

	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel())) {
		 UserCentral.getInstance().getMainchanUsers().remove(sender);
		 UserCentral.getInstance().getMainChanUserPrefixes().remove(sender);
	  } else if (channel.equalsIgnoreCase(Main.getBotSettings().getAdminChannel())) {
		 UserCentral.getInstance().getAdminchanUsers().remove(sender);
	  }
	  if (!UserCentral.getInstance().getMainchanUsers().contains(sender)
			&& !UserCentral.getInstance().getAdminchanUsers().contains(sender)) {
		 UserCentral.getInstance().unauthenticateUser(sender);
	  }
   }

   @Override
   protected void onPrivateMessage(final String sender, final String handler, final String message) {
	  if (isAuthorizing || "".equals(UserCentral.getInstance().getOnlineUser(sender)))
		 return;
	  String preferredHandler = handler;
	  if ("".equals(preferredHandler)) {
		 preferredHandler = Main.getBotSettings().getBotNick();
	  }
	  String formatMessage = Parser.isCommand(message.trim());
	  if (!"".equals(formatMessage)) {
		 // it was a command
		 IRCCommand command = Parser.parseCommand(formatMessage, Constants.PM_SOURCE, sender, sender);
		 Command cmdObject = Command.getCommand(command.get(0));
		 if ("".equals(UserCentral.getInstance().getOnlineUser(sender))
			   && (cmdObject == null || !Command.getCommand(command.get(0)).isPublicAccess()) || adminlock
			   && !UserCentral.getInstance().onlineUserIsAdmin(sender, ""))
			return;
		 if (!"".equals(UserCentral.getInstance().getOnlineUser(sender))) {
			UserHandler.addUserActivity(UserCentral.getInstance().getOnlineUser(sender), sender);
		 }
		 CommandCentral.addCommand(command);
	  } else {
		 // it wasn't a command, so check if it was news, help adding or
		 // intel or such
		 if ("".equals(UserCentral.getInstance().getOnlineUser(sender)))
			return;
		 else {
			int newOrdersCheck = KDHandler.getNewOrdersAmount(sender);
			if (newOrdersCheck > 0 && !adminlock
				  && !"".equals(CommonMethods.getProv(UserCentral.getInstance().getOnlineUser(sender)))) {
			   sendMessage(sender, Colors.RED + newOrdersCheck
					 + " new orders have been added since you last checked. Please check "
					 + Main.getBotSettings().getCommandPrefix() + "orders");
			}
			UserHandler.addUserActivity(UserCentral.getInstance().getOnlineUser(sender), sender);
		 }
		 TextInputCentral.addTextInput(message.trim(), sender, sender, preferredHandler);
	  }
   }

   @Override
   protected void onQuit(final String sourceNick, final String sourceLogin, final String sourceHostname, final String reason) {
	  String nick = CommonMethods.getUserFromDB(sourceNick, false);
	  if ("".equals(nick)) {
		 UserHandler.addLastSeen(sourceNick, "Saw " + sourceNick + " quit with the message " + reason);
	  } else {
		 UserHandler.addLastSeen(nick, "Saw " + nick + " quit with the message " + reason);
	  }

	  UserCentral.getInstance().unauthenticateUser(sourceNick);
	  UserCentral.getInstance().getMainchanUsers().remove(sourceNick);
	  UserCentral.getInstance().getMainChanUserPrefixes().remove(sourceNick);
	  UserCentral.getInstance().getAdminchanUsers().remove(sourceNick);
   }

   @Override
   protected void onServerResponse(final int code, final String response, final String channel) {
	  if (code == 473 || code == 474 || code == 475) {
		 if (response.split(" ")[1].equals(Main.getBotSettings().getMainChannel())) {
			sendRawLine("cs invite " + Main.getBotSettings().getMainChannel());
		 } else if (response.split(" ")[1].equals(Main.getBotSettings().getAdminChannel())) {
			sendRawLine("cs invite " + Main.getBotSettings().getAdminChannel());
		 }
	  }
   }

   @Override
   protected void onSop(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) && !sourceNick.equals(getNick()) && !workersContain(sourceNick)) {
		 if (UserCentral.getInstance().getMainChanUserPrefixes().containsKey(recipient)) {
			UserCentral
				  .getInstance()
				  .getMainChanUserPrefixes()
				  .put(recipient,
						UserCentral.getInstance().setUserPrefix(
							  UserCentral.getInstance().getMainChanUserPrefixes().get(recipient), true, "a"));
		 } else {
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(recipient, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), true, "a"));
		 }
		 BotHandler.updateSilenceListPrefix(recipient);
	  }
   }

   @Override
   protected void onTopic(final String channel, final String topic, final String setBy, final long date, final boolean changed) {
	  TopicCentral.getInstance().setTopic(channel, topic);
   }

   @Override
   protected void onUserList(final String channel, final User[] users) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel())) {
		 isAuthorizing = !Main.getBotSettings().requirePasswordAuth();
		 String nick = "";
		 for (final User u : users) {
			nick = u.getNick();
			if (!UserCentral.getInstance().getMainchanUsers().contains(nick)) {
			   UserCentral.getInstance().getMainchanUsers().add(nick);
			}
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(nick, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), true, u.getPrefix()));
			if (!Main.getBotSettings().requirePasswordAuth() && !nick.equals(getNick()) && !workersContain(nick)) {
			   toAuthenticateList.add(nick);
			}
		 }
		 if (isAuthorizing) {
			String check = "";
			int added = 0;
			for (String user : toAuthenticateList) {
			   if (added > 15) {
				  doStatusCheck(check.trim());
				  check = "";
				  added = 0;
			   }
			   check += user + " ";
			   ++added;
			}
			if (added > 0) {
			   doStatusCheck(check.trim());
			}
		 }
	  } else if (channel.equalsIgnoreCase(Main.getBotSettings().getAdminChannel())) {
		 for (final User u : users) {
			final String nick = u.getNick();
			if (!UserCentral.getInstance().getAdminchanUsers().contains(nick)) {
			   UserCentral.getInstance().getAdminchanUsers().add(nick);
			}
		 }
		 for (final String s : UserCentral.getInstance().getAdminchanUsers()) {
			if (!UserCentral.getInstance().getAuthenticatedUsers().containsKey(s)) {
			   doStatusCheck(s);
			}
		 }
	  }
   }

   @Override
   protected void onVoice(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
	  if (channel.equalsIgnoreCase(Main.getBotSettings().getMainChannel()) && !sourceNick.equals(getNick()) && !workersContain(sourceNick)) {
		 if (UserCentral.getInstance().getMainChanUserPrefixes().containsKey(recipient)) {
			UserCentral
				  .getInstance()
				  .getMainChanUserPrefixes()
				  .put(recipient,
						UserCentral.getInstance().setUserPrefix(
							  UserCentral.getInstance().getMainChanUserPrefixes().get(recipient), true, "v"));
		 } else {
			UserCentral.getInstance().getMainChanUserPrefixes()
				  .put(recipient, UserCentral.getInstance().setUserPrefix(new UserPrefixes(), true, "v"));
		 }
		 BotHandler.updateSilenceListPrefix(recipient);
	  }
   }

   /**
    * Reloads army and alarm timers when the bot's been restarted or reconnected
    */
   private void reloadTimers() {
	  timerHandler.reloadArmyTimers();
	  timerHandler.reloadAlarmTimers();
   }

   /**
    * Adds the IRCOutput to the outgoing queue, given that it actually has content and somewhere to send that content
    * 
    * @param out
    */
   public void send(final IRCOutput out) {
	  if (out != null && !out.getOutput().isEmpty() && !"".equals(out.getAddress())) {
		 OutputQueue.INSTANCE.add(out);
	  }
   }

   @Override
   public void sendMessage(final String target, final String message) {
	  IRCOutput output = new IRCOutput(0, "", target, OutputTypeEnum.RETURN_TO_SOURCE_MESSAGE, 2);
	  output.addOutput(message);
	  send(output);
   }

   @Override
   public void sendNotice(final String target, final String message) {
	  IRCOutput output = new IRCOutput(-1, "", target, OutputTypeEnum.RETURN_TO_SOURCE_NOTICE, 2);
	  output.addOutput(message);
	  send(output);
   }

   /**
    * Sends a welcome message to the specified user
    * 
    * @param nick
    */
   public void sendWelcomeMessage(final String nick) {
	  String mainNick = UserCentral.getInstance().getOnlineUser(nick);
	  UserHandler.checkForMessage(mainNick, nick);
	  BotHandler.checkForWait(nick);
	  sendNotice(nick, "Successfully authenticated");
	  UtopiaTime uttime = new UtopiaTime(GregorianCalendar.getInstance().getTimeInMillis());
	  sendNotice(nick, Colors.OLIVE + "Current utotime: " + Colors.BLUE + uttime.formattedUT());
	  sendNotice(nick, "There are "
			+ KDHandler.getAmountOfEvents()
			+ " events ("
			+ Main.getBotSettings().getCommandPrefix()
			+ "events)"
			+ (KDHandler.isWaveScheduled() ? " and a wave (" + Main.getBotSettings().getCommandPrefix() + "wave)"
				  : " and no wave") + " scheduled");
	  sendNotice(nick, "There are " + KDHandler.getAmountOfAidRequests() + " active aid requests ("
			+ Main.getBotSettings().getCommandPrefix() + "aid)");

	  onMessage(Main.getBotSettings().getMainChannel(), nick, "", Main.getBotSettings().getCommandPrefix() + "army");
	  onMessage(Main.getBotSettings().getMainChannel(), nick, "", Main.getBotSettings().getCommandPrefix() + "spells");

	  int newOrdersCheck = KDHandler.getNewOrdersAmount(nick);
	  if (newOrdersCheck > 0) {
		 sendNotice(nick, Colors.RED + newOrdersCheck + " new orders have been added since you last checked. Please check "
			   + Main.getBotSettings().getCommandPrefix() + "orders");
	  }
   }

   /**
    * Locks the bot so that only admins can use commands, or unlocks it
    * 
    * @param lock
    */
   public void setAdminLock(final boolean lock) {
	  adminlock = lock;
   }

   /**
    * Starts the intel listener service
    */
   public final void startIntelListener() {
	  IntelServerType ist = Main.getBotSettings().getIntelServerType();
	  switch (ist) {
	  case JETTY_SERVER:
		 Executors.newSingleThreadExecutor().execute(new JettyServer());
		 break;
	  case EXTERNAL_SERVER:
		 Executors.newSingleThreadExecutor().execute(new IntelListener());
		 break;
	  }
   }

   /**
    * Schedules a timer to go off every tick
    */
   private void tickTimer() {
	  if (tickTimer != null) {
		 tickTimer.cancel();
	  }
	  tickTimer = new Timer();
	  GregorianCalendar cal = (GregorianCalendar) Calendar.getInstance();
	  cal.add(Calendar.MINUTE, Main.getBotSettings().getTickLength());
	  cal.setTimeInMillis(cal.getTimeInMillis() - cal.getTimeInMillis() % (Main.getBotSettings().getTickLength() * 60L * 1000));
	  tickTimer.schedule(new HourlyTask(tickTimer), cal.getTimeInMillis() - GregorianCalendar.getInstance().getTimeInMillis());
   }

   protected void workerDisconnect() {
	  if (!plannedDisconnect) {
		 disconnectFromIRC();
		 try {
			Thread.sleep(60000);
		 } catch (final InterruptedException e) {
		 }
		 connectToIRC();
	  }
	  plannedDisconnect = false;
   }

   /**
    * Checks if the specified nick belongs to a worker
    * 
    * @param nick
    * @return
    */
   public boolean workersContain(final String nick) {
	  for (Worker worker : workers) {
		 if (worker.nick.equals(nick))
			return true;
	  }
	  return false;
   }
}
